package edu.hawaii.halealohacli.processor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * Contains static methods to load classes via the Java reflection API.
 * 
 * @author Leo deCandia
 * @author http://snippets.dzone.com/posts/show/4831
 * @author http://stackoverflow.com/questions/205573/
 */
@SuppressWarnings("rawtypes")
public class CommandFinderUtils {

  /**
   * Gets the classes.
   * 
   * @param packageName the package name
   * @return the classes that are on the classpath
   * @throws ClassNotFoundException the class not found exception
   * @throws IOException Signals that an I/O exception has occurred.
   */
  public static List<Class> getClassesBothJarAndFile(String packageName)
      throws ClassNotFoundException, IOException {
    List<Class> classesInJars = getClassesJar(packageName);
    List<Class> classesInFiles = getClasses(packageName);
    classesInJars.addAll(classesInFiles);
    return classesInJars;
  }

  /**
   * Gets the classes2.
   * 
   * @param packageName the package name
   * @return the classes that are on the classpath
   */
  public static List<Class> getClassesJar(String packageName) {

    List<Class> classes = new ArrayList<Class>();

    for (String classpathEntry : System.getProperty("java.class.path").split(
        System.getProperty("path.separator"))) {
      if (classpathEntry.contains("hale-aloha-cli-grads") && classpathEntry.endsWith(".jar")) {
        //System.out.println("classpathEntry " + classpathEntry);
        File jar = new File(classpathEntry);
        JarInputStream is = null;
        try {
          is = new JarInputStream(new FileInputStream(jar));

          JarEntry entry;
          while (true) {
              entry = is.getNextJarEntry();
              if (entry == null) {
                break;
              }
              // System.out.println("class: " + entry);
              if (!entry.getName().contains("$") && entry.getName().endsWith(".class")
                  && entry.getName().replace('/', '.').contains(packageName)) {

                Class klass;
                try {
                  int length = entry.getName().length();
                  String name = entry.getName().replace('/', '.').substring(0, length - 6);
                  //System.out.println(name);
                  klass = Class.forName(name);
                  classes.add(klass);
                }
                catch (ClassNotFoundException e) {
                  // e.printStackTrace();
                  System.out.print(".");
                }
              }
          }
        }
        catch (FileNotFoundException e) {
          System.err.println("Command not found: " + e.toString());
          //e.printStackTrace();
        }
        catch (IOException e) {
          System.err.println("File IO error: " + e.toString());
        }
        finally {
          if (is != null) {
            try {
              is.close();
            }
            catch (IOException e) {
              System.err.println("unable to close jar file: " + is.toString());
            }
          }
        }

      }
    }

    return classes;

  }

  /**
   * Scans all classes accessible from the context class loader which belong to the given package
   * and subpackages.
   * 
   * @param packageName The base package
   * @return The classes
   * @throws ClassNotFoundException the class not found exception
   * @throws IOException Signals that an I/O exception has occurred.
   */
  public static List<Class> getClasses(String packageName) throws ClassNotFoundException,
      IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
      URL resource = resources.nextElement();
      String fileName = resource.getFile();
      String fileNameDecoded = URLDecoder.decode(fileName, "UTF-8");
      dirs.add(new File(fileNameDecoded));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
      classes.addAll(findClasses(directory, packageName));
    }
    return classes;
  }

  /**
   * Recursive method used to find all classes in a given directory and subdirs.
   * 
   * @param directory The base directory
   * @param packageName The package name for classes found inside the base directory
   * @return The classes
   * @throws ClassNotFoundException the class not found exception
   */
  private static List<Class> findClasses(File directory, String packageName)
      throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
      return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
      String fileName = file.getName();
      if (file.isDirectory()) {
        assert !fileName.contains(".");
        classes.addAll(findClasses(file, packageName + "." + fileName));
      }
      else if (fileName.endsWith(".class") && !fileName.contains("$")) {
        Class childClass;
        try {
          childClass =
              Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6));
        }
        catch (ExceptionInInitializerError e) {
          // happen, for example, in classes, which depend on
          // Spring to inject some beans, and which fail,
          // if dependency is not fulfilled
          childClass =
              Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6),
                  false, Thread.currentThread().getContextClassLoader());
        }
        classes.add(childClass);
      }
    }
    return classes;
  }

}
